// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: gekim

#pragma once

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include <boost/date_time/gregorian/gregorian.hpp>
#include <string>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/base/date_util.h"
#include "coin2/base/string_util.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/api_util/json_schema.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"
#include "coin2/exchange/util/json_util.h"

namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;
using namespace coin::proto;

class KucoinFuturesFeedUpdateParser {
 public:
  static void ParseMarkIndexPrice(
      const rapidjson::Value& ticker_json, int64_t ts,
      coin2::exchange::base::feed::impl::FeedUpdateManager* upd_mgr,
      const std::string& native_symbol) {
    if (ticker_json.HasMember("markPrice")) {
      auto* mark_price = upd_mgr->GetMarkPriceMutable();
      mark_price->timestamp = ts;
      mark_price->mark_price = std::stod(ticker_json["markPrice"].GetString());
      upd_mgr->PublishMarkPriceWithExchangeTimeCheck(native_symbol);
    }
    if (ticker_json.HasMember("indexPrice")) {
      auto* index = upd_mgr->GetIndexMutable();
      index->timestamp = ts;
      index->price = std::stod(ticker_json["indexPrice"].GetString());
      upd_mgr->PublishIndexWithExchangeTimeCheck(native_symbol);
    }
  }

  static bool ParseBookL2(BookBuilder* book_builder_,
                         const rapidjson::Value& book_json,
                         bool is_light_bbo) {
    auto ts =
        std::llround(stod(book_json["timestamp"].GetString()) * 1'000'000LL);
    auto tx = book_builder_->NewTransaction(ts);
    if (is_light_bbo) {
      tx.BboClear();
    }
    auto splited = Split(book_json["change"].GetString(), ",");
    CHECK_EQ(splited.size(), 3);
    auto& price = splited[0];
    auto& side = splited[1];
    auto& qty = splited[2];

    if (side == "buy") {
      tx.UpdateBid(stod(price), stod(qty));
    } else if (side == "sell") {
      tx.UpdateAsk(stod(price), stod(qty));
    } else {
       NOTREACHED() << "Unkown side " << side;
    }
    return true;
  }
 public:
  static void ParseFundingRate(
      const rapidjson::Value& ticker_json, int64_t ts,
      coin2::exchange::base::feed::impl::FeedUpdateManager* upd_mgr,
      const std::string& native_symbol) {
    if (ticker_json.HasMember("value") &&
        ticker_json.HasMember("predictedValue") &&
        ticker_json.HasMember("timePoint")) {
      auto* funding_rate = upd_mgr->GetFundingRateMutable();
      funding_rate->timestamp = ts;
      funding_rate->funding_rate = std::stod(ticker_json["value"].GetString());
      funding_rate->estimated_rate =
          std::stod(ticker_json["predictedValue"].GetString());
      funding_rate->funding_time =
          std::stoll(ticker_json["timePoint"].GetString()) * 1'000'000LL;

      upd_mgr->PublishFundingRateWithExchangeTimeCheck(native_symbol);
    }
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(std::string_view interval) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"5", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"1", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"30", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"60", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"240", fastfeed::proto::KLINE_INTERVAL_4HOUR},
      {"1440", fastfeed::proto::KLINE_INTERVAL_1DAY}
    };

    auto iter = interval_map.find(std::string(interval));
    if (iter == interval_map.end()) {
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

  static bool ParseKlineRest(
      const rapidjson::Value& msg,
      Klines* klines,
      fastfeed::proto::KlineInterval interval,
      int64_t timestamp) {
    const auto& v = msg.GetArray();
    CHECK_EQ(v.Size(), 6);
    int64_t open_timestamp = stod(v[0].GetString()) * 1'000'000LL;
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();
    kline->open_timestamp = open_timestamp;
    kline->open = stod(v[1].GetString());
    kline->high = stod(v[2].GetString());
    kline->low = stod(v[3].GetString());
    kline->close = stod(v[4].GetString());
    kline->volume = stod(v[5].GetString());
    kline->timestamp = timestamp;
    kline->interval = interval;

    return freezed;
  }
};

}  // namespace impl

using impl::KucoinFuturesFeedUpdateParser;
